package cn.aitrox.ry.app.web.aop;

import cn.aitrox.ry.bean.RespResult;
import cn.aitrox.ry.common.exception.CustomBizException;
import cn.aitrox.ry.enumtype.RespCodeEnum;
import cn.aitrox.ry.util.LogUtil;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

@ControllerAdvice
public class CustomExceptionHandler {

    private static Logger log = LoggerFactory.getLogger(CustomExceptionHandler.class);

    @ExceptionHandler(value = {Exception.class})
    public ResponseEntity<RespResult> ExceptionHandler(Exception e) {
        RespResult rr = null;
        try {
            if (e instanceof MethodArgumentNotValidException)
                rr = this.validErrorHandler((MethodArgumentNotValidException) e);
            else if (e instanceof CustomBizException) // 业务错误(CustomBizException)
                rr = this.errorHandler((CustomBizException) e);
            else if (e.getClass().getName().equals("org.springframework.security.access.AccessDeniedException"))
                rr = new RespResult(RespCodeEnum.NO_PERMISSION);
            else // 系统错误
                rr = this.exceptionHandler(e);
        } catch (Exception ex) {
            log.error("ExceptionHandler inner Exception", ex);
            rr = new RespResult(RespCodeEnum.SYSTEM_ERROR);
        }

        return new ResponseEntity<RespResult>(rr, HttpStatus.OK);
    }

    private RespResult validErrorHandler(MethodArgumentNotValidException e) {
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        List<String> msgList = new ArrayList<>();
        for (ObjectError allError : allErrors) {
            msgList.add(allError.getDefaultMessage());
        }
        return RespResult.secByError(RespCodeEnum.PARAM_ERROR.getCode(), StringUtils.joinWith(";", msgList.toArray()));
    }

    private RespResult errorHandler(CustomBizException cbe) {
        return RespResult.secByError(cbe.getCode(), cbe.getMessage());
    }

    private RespResult exceptionHandler(Exception e) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String uri = request.getRequestURI();
        String data = Joiner.on("&").withKeyValueSeparator("=").join(getRequestParams(request));
        log.error(LogUtil.gen("ExceptionHandler Exception", "uri", uri, "data", data), e); // log error所有系统异常

        // Preconditions的异常：进行特殊处理返回ApiCodeEnum.PARAM_ERROR
        StackTraceElement[] stackTraceElements = e.getStackTrace();
        if (stackTraceElements != null && stackTraceElements.length > 0 && stackTraceElements[0].getClassName() == Preconditions.class.getName()) {
            String message = null;
            switch (stackTraceElements[0].getMethodName()) {
                case "checkNotNull":
                    return RespResult.secByError(RespCodeEnum.PARAM_ERROR.getCode(), "不能为空");
                case "checkArgument":
                default:
                    return RespResult.secByError(RespCodeEnum.PARAM_ERROR.getCode(), "错误");
            }
        }
        return RespResult.secByError(RespCodeEnum.SYSTEM_ERROR.getCode(), e.getMessage());
    }

    private static Map<String, String> getRequestParams(HttpServletRequest request) {
        Map<String, String> map = Maps.newHashMap();
        Enumeration<String> paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String paramName = (String) paramNames.nextElement();

            String[] paramValues = request.getParameterValues(paramName);
            if (paramValues.length == 1) {
                String paramValue = paramValues[0];
                if (paramValue.length() != 0) {
                    map.put(paramName, paramValue);
                }
            }
        }

        return map;
    }
}